<template>
	<div class="uploadByCropper">
		<h2>This is an 	upload By Cropper's page</h2>
		<div><a v-show="imgSrc!=''" :href="imgSrc">图片线上地址点击我:{{imgSrc}}</a></div>
		<el-button @click="open">点我</el-button>
		<el-dialog
      title="upload avatar"
      :visible="isShowModel"
      :before-close="closecropperModel"
      class="Cropperupload"
      :class="tCropWidth > 200 ? 'lcropper' : 'scropper'"
      @close="close"
      append-to-body
      :close-on-click-modal="false"
    >
      <div
        class="cropper_content"
        :class="{ small: tCropHeight <= 300, big: tCropHeight > 300 }"
      >
        <el-row :gutter="10">
          <el-col :span="16" class="imageArea" v-if="cropperOption.image">
            <vueCropper
              ref="cropper"
              :img="cropperOption.image ? cropperOption.image : dealImage"
              :autoCrop="true"
              :info="true"
              :full="false"
              outputType="png"
              :fixedBox="fixedBox"
              :autoCropWidth="cropperOption.autoCropWidth"
              :autoCropHeight="cropperOption.autoCropHeight"
              :outputSize="cropperOption.outputSize"
              @realTime="realTime"
            >
            </vueCropper>
          </el-col>
          <el-col :span="8">
            <div
              class="prewArea"
              :style="tCropWidth > 200 ? previewStyleBig : previewStyle"
            >
          
            </div>
            <el-row :gutter="10">
              <el-col :span="22">
                <div class="changeImage">
                  <label class="btn" for="uploads">Choose image</label>
                  <!-- 此处是input元素的文件属性，如果未指定文件上传个数，那么默认只允许用户添加一个文件，需要多文件上传，在input后面添加上  multiple属性即可，例子：<input type="file" multiple > -->
                  <input
                    type="file"
                    ref="uploads"
                    id="uploads"
                    style="position: absolute; clip: rect(0 0 0 0)"
                    accept="image/png, image/jpeg, image/gif, image/jpg"
                    @change="setImage($event, 1)"
                  />
                </div>
              </el-col>
            </el-row>
            <el-row :gutter="10" v-if="cropperOption.image">
              <el-col :span="11">
                <!-- 放大 -->
                <el-button type="success" @click="changeScale(1)"
                  >Enlarge</el-button
                >
              </el-col>
              <el-col :span="11">
                <!-- 缩小 -->
                <el-button type="success" @click="changeScale(-1)"
                  >Shrink</el-button
                >
              </el-col>
            </el-row>
            <el-row :gutter="10" v-if="cropperOption.image">
              <el-col :span="11">
                <!-- 左旋转 -->
                <el-button type="success" @click="rotateLeft"
                  ><span>rotate(+)</span></el-button
                >
              </el-col>
              <el-col :span="11">
                <!-- 右旋转 -->
                <el-button type="success" @click="rotateRight"
                  ><span>rotate(-)</span></el-button
                >
              </el-col>
            </el-row>
            <el-row :gutter="10" v-if="cropperOption.image">
              <el-col :span="11">
                <!-- 上传 -->
                <el-button type="success" @click="uploadImg('blob')"
                  >upload</el-button
                >
              </el-col>
              <el-col :span="11">
                <!-- 取消 -->
                <el-button type="success" @click="cancelUpload"
                  >cancel</el-button
                >
              </el-col>
            </el-row>
          </el-col>
        </el-row>
      </div>
      <div class="footer-btn">
        <div class="scope-btn">
          <div class="loadImg"></div>
        </div>
      </div>
    </el-dialog>
	</div>
</template>

<script>
import axios from "axios"; //axios用于网络请求
	export default{
		name:'uploadByCropper',
	 props: {
    //  vue-cropper的一些配置
    isShowCropper: {
      //是否显示上传图片的组件
      type: Boolean,
      default: true,
    },
    fixedBox: {
      type: Boolean,
      default: true,
    },
    tCropWidth: {
      //默认生成截图框宽度
      type: Number,
      default: 0.3 * innerWidth,
    },
    tCropHeight: {
      //默认生成截图框高度
      type: Number,
      default: 0.3 * innerWidth,
    },
    dealImage: {
      //默认生成截图框图片的删除后的样式
      type: String,
      default:
        "",
    },
    // getChangedImage: {
    //   type: Function,
    //   default: () => {}
    // },
    dataSize: {
      type: Number,
      default: 2, //图片最大内存
    },
    // 上传七牛云的一些配置
    uploadArea: {
      //七牛云的bucket所在的地区 所绑定的域名,即你创建的七牛云空间 所在的地区的域名
      type: String,
      default: "https://upload-z2.qiniup.com",//此处为七牛云华南区的空间地址
    },
    uploadUrl: {
      //你的七牛云bucket所绑定的域名，即你创建的七牛云空间绑定的域名
      type: String,
      default: "http://hmytest.humianyuan.cn/", //上传的域名地址
    },
  },
  data() {
    return {
      // 预览样式，正常图片和大图片的预览样式不一样
      previewStyle: {
        width: "25vw",
        "max-height": "25vw",
        overflow: "hidden",
        'background-color':'#8c8c8c',
        zoom: "0.75",
      },
      previewStyleBig: {
        overflow: "hidden",
        zoom: "0.23",
        display: "flex",
        width: "100%",
        "justify-content": "center",
        'background-color':'#8c8c8c',
        transform: "translateX(-44px)",
      },
      // fileList: [], // 用来存放多张图片名称，单张图片上传，不需要数组存放,所以此处注释掉
      fileName: "", //用来存放重命名好后的图片名称
      previews: {},
      fileUpload: null,
      // vue-cropper的配置
      cropperOption: {
        outputSize: 1, //裁剪生成图片的质量
        full: false, //是否输出原图比例的截图
        autoCrop: true, //是否默认生成截图框
        fixedBox: false, //固定截图框大小 不允许改变
        autoCropWidth: this.tCropWidth, //默认生成截图框宽度
        autoCropHeight: this.tCropHeight, //默认生成截图框高度
        image: null, //裁剪图片的地址, 使用URL.createObjectURL配合文件对象可以实现图片预览的功能
      },
      isShowModel: this.isShowCropper,
      filePath_or_stream:'',//图片的数据流
      F:'',//图片的文件对象
			token:'',
			imgSrc:'',//图片线上地址
    };
  },
  methods: {
    // 用户上传图片前有两个操作：1.点击选择图片按钮 2.选择需要上传的图片 这个时候鼠标触发两个事件，分别对应下面的两个【e】
    setImage(e, fileNum) {
      console.log("同步代码区域1开始**************************")
      //【参数e】用于获取鼠标点击的元素,此处的e由鼠标点击表单,由input的文件属性中的change函数事件触发; 【fileNum】用来指定上传文件的个数，由于上面元素的点击函数【html代码：37行处】我已经定义为1,所以此处fileNum默认为1
      console.log("fileNum:", fileNum); //输出我定义的默认值 1
      console.log("鼠标点击对象", e.target); //输出此时鼠标点击的对象，即表单元素input
      console.log("第一个e", e); //输出Event {isTrusted: true, type: "change",...
      var file = e.target.files[0]; //获取用户点击的第一个文件
      this.F=file
      this.fileUpload = file; //把文件信息赋给fileUpload
      console.log("上传文件的信息：", this.fileUpload); //假设名称为‘z&^中文.gif’的图片 将会输出:  File {name: "z&^中文.gif", lastModified:....
      if (!/\.(gif|jpg|jpeg|png|bmp|GIF|JPG|PNG)$/.test(e.target.value)) {
        this.$message.warning("图片类型必须是.gif,jpeg,jpg,png,bmp中的一种");
        return false;
      }
      console.log("同步代码区域1结束**************************")
			//1.2文件读取
      var reader = new FileReader(); //文件读取
      reader.onload = (e) => {
        //【参数e】用于获取鼠标点击的元素,此处的e由鼠标点击文件,由读取文件函数触发
        console.log('')//分割打印信息
        console.log("异步读取文件代码区域开始**************************")
        console.log("鼠标点击对象", e.target); //输出此时鼠标点击的对象，即文件元素FileReader
        console.log("第二个e", e); //输出: ProgressEvent{isTrusted: true, lengthComputable: true, loaded....
        let data;
        if (typeof e.target.result === "object") {
          //Array Buffer是一个对象，此处若读取到的文件是一个对象，那么它就是buffer,若是字符串则为二进制，则跳到下面的else执行
          console.log("e.target.reault：", e.target.result); //假设我文件的buffer大小是2653466, 此处将会输出：ArrayBuffer(2653466){byteLength：2653466}  解释一下buffer是计算机用于缓冲内存的概念，简单解释就是先开放一个内存，用来存放你的文件，待到计算机处理完其它需要处理的事情，再来读取你的buffer里面的数据
          console.log("reder.result", reader.result); //reader.result==e.target.result
          console.log(
            "reader.result==e.target.result",
            reader.result == e.target.result
          ); //打印结果：reader.result==e.target.result true
          // 把Array Buffer转化为blob 如果是base64不需要
          //下面的是URL.createObjectURL的用法，详细用法自查mdn, 主要是把字符串追加到当前的url地址后面 生成一个预览地址
          this.filePath_or_stream = new Blob([e.target.result])
          console.log("文件流：", this.filePath_or_stream)
        data = window.URL.createObjectURL(this.filePath_or_stream); //使用Blob转化buffer为二进制,把二进制数据追加到当前文件所在的url地址符后面,可以实现图片预览的功能
          console.log(
            "data = window.URL.createObjectURL(new Blob([e.target.result])):",
            data
          ); //输出：blob:http://localhost:8080/5736c21f-f5af-49ff-aa6a-48e40f8322c4..........
          // 注意：使用
          //  data = new Blob([e.target.result])
          //  console.log("data type:",data)
        } else {
          //如果读取到的不是buffer对象，那么读取到的就是二进制流，或者base64格式的数据流，此处区别上面的文件内存buffer是由于buffer数据是 而这里的数据是字符串：【二进制】 或者 【base64的字符串】，即把原来的3个8位二进制的字符串拆分为4个6位的二进制，再对每一个6位二进制进行补零操作生成4个8位2进制的 然后再把每8位二进制的字符为一组，转换为base64规定的64位字符中的某一个
          data = e.target.result; //二进制流或者base64流数据赋值给data
          console.log("data = e.target.result", data);
          console.log("reder.result", reader.result);
        }
        if (fileNum === 1) {
          //此处fileNum===1，是由于我表单数据的点击函数【html代码：37行处】传入了参数为1,是我自定义的默认值，不用判断也行，此处非重点
          this.cropperOption.image = data; //把二进制流数据赋给打开的图像 此时生成一个图片预览的地址符，可以实现预览
          console.log("this.cropperOption.image:", this.cropperOption.image);
        }
        this.$refs["uploads"].value = "";
        console.log("异步读取文件代码区域结束**************************");
      };

      //下面为文件读取操作可选值有很多【注意：读取操作为异步操作】：具体查mdn的文件接口文档：查FileReader的使用
      // reader.readAsArrayBuffer(file) 异步按字节读取文件内容，结果用ArrayBuffer对象表示
      // reader.readAsBinaryString(file) 异步按字节读取文件内容，结果为文件的二进制串
      // reader.readAsDataURL(file) 异步读取文件内容，结果用data:url(即Base64格式)的字符串形式表示
      // reader.readAsText(file, encoding) 异步按字符读取文件内容，结果用字符串形式表示

      // 下面展示两种读取操作：
      // 异步操作1,readAsDataURL 此函数把数据转化为base64的数据流, 这里会触发上面的reader.onload，读取结束后会把结果传入上面的reader.onload里面
      //  let a = reader.readAsDataURL(file)//以base64的数据流读取
      //  console.log("a是同步代码,此时a返回值:",a)//由于reader是异步操作,a是同步函数;读取还没结束时，a已经输出,所以输出a undefined

      //异步操作2,readAsArrayBuffer 此函数把数据转化为buffer流 这里会触发上面的reader.onload，读取结束后会把结果传入上面的reader.onload里面
      let b = reader.readAsArrayBuffer(file); //以buffer流读取
      console.log('')//分割打印信息
      console.log("同步代码区域2开始**************************")
      console.log("b是同步代码,此时b返回值:", b); //由于reader是异步操作,b是同步函数;读取还没结束时，b已经输出,所以输出b undefined
      console.log("file对象信息:", file);
      console.log("this.fileUpload对象信息:", this.fileUpload);
      this.fileName = this.fileUpload.name; //图片文件名称 过滤中文字符及特殊字符,重新生成文件名称
      this.fileName = this.filter_fileNameStr(this.fileName, this.getUniqueID); // 第一个参数文件名,第二个参数是生成唯一ID的函数
      this.$emit('generateFileName',this.fileName);//把生成的文件名发给父组件,通知父组件请求后台生成token
      console.log("文件处理后名称为：", this.fileName);
      console.log("同步代码区域2结束***************************")

      // 获取服务器token
      axios.get("/api_hu66/upload_byfront/token").then((res) => {
          console.log("*********************************")
          console.log("接收到后台传来的token：",res.data.token);
          this.token = res.data.token;
        })
    },

    // 放大缩小 num为正数时放大，负数为缩小，ref调用vue-cropper组件的函数
    changeScale(num) {
      console.log("放大缩小的num1：", num);
      num = num || 1;
      console.log("放大缩小的num2：", num);
      this.$refs.cropper.changeScale(num);
    },

    // 左旋转 ref调用vue-cropper组件的函数
    rotateLeft() {
      this.$refs.cropper.rotateLeft();
    },

    // 右旋转 ref调用vue-cropper组件的函数
    rotateRight() {
      this.$refs.cropper.rotateRight();
    },

    // 上传操作
    uploadImg(type) {
      if (type === "blob") {
        this.$refs.cropper.getCropBlob((data) => {//cropper也内置了生成图片二进制流的函数
          let img = window.URL.createObjectURL(data);
          console.log("二进制流，",data)
          console.log("img的url二进制流",img)
          if (data.size / 1024 / 1024 > this.dataSize) {
            this.$message({
              message: "上传图片不能超过" + this.dataSize,
              type: "warning",
            });
            return false;
          }
          // formData.append这个方法有两个版本 一个有两个参数的版本和一个有三个参数的版本。
          // formData.append(name, value); 键：name 值：value
          // formData.append(name, value, filename); 前两个参数同上，最后一个为上传给服务器的文件的名称
          // formData.append("fileName", data, this.fileName);//这一句相当于后面这两句的和：formData.append("fileName", data,) formData.append("key",this.fileName)
          // formData.append("file", data,) //此处直接使用cropper内置的函数生成的二进制data
          // formData.append("file",this.filePath_or_stream)//这个是我们自定义的setImg函数生成的二进制数据流，查看上面第249行的赋值操作
  
          const formdata = new FormData();
          formdata.append("file", this.F);//向表单追加文件数据 的键值对
          formdata.append("token", this.token);//向表单追加token 的键值对
          formdata.append("key", this.fileName);//向表单追加文件名称 的键值对
          // 发送七牛云 参数分别是 1.七牛云华南区的地址【你开辟bucket空间的所在区域的域名】 2.表单数据【即我们需要上传的文件数据】 3.设置header请求头的媒体类型为，用来通知无需对此数据编码再发送，因为普通表单数据传输的时候会进行编码，但是此处如果是二进制数据的话则无需编码
         
         // 请求头的Content-Type 用于指示资源的MIME类型【media type】
          // 设置媒体类型为 multipart/form-data，表示此时的表单数据携带的是二进制流数据
				 axios.post(this.uploadArea,formdata, {
              headers: { "Content-Type": "multipart/form-data" },
            }).then((res) => {
              console.log("*********************************")
              console.log("上传服务器后传回的响应数据：",res)
              console.log("图片访问地址:",this.uploadUrl+this.fileName);
              this.imgSrc = this.uploadUrl+this.fileName;//生成图片地址
							 this.$message({
          message: '上传成功!关闭弹框就可以看见线上地址了',
          type: 'success'
        });
              })
        });
      } else {
        // 预留
      }
    },

    // 取消上传
    cancelUpload() {
      this.fileUpload = null;
      this.cropperOption = {
        outputSize: 1, //裁剪生成图片的质量
        autoCrop: true, //是否默认生成截图框
        fixedBox: false, //固定截图框大小 不允许改变
        autoCropWidth: this.tCropWidth,
        autoCropHeight: this.tCropHeight,
        image: null,
      };
      this.previews = {};
      if (this.cropperOption.image) {
        this.$refs["cropper"].imgs = "";
      }

      this.$emit("imgUploadstatus", false); //图片上传状态
    },

    // 预览
    realTime(data) {
      this.previews = {
        ...data,
      };
    },
		open(){
this.isShowModel = true
		},
		close(){
			this.isShowModel = false;
		},
    // 关闭弹窗之前
    closecropperModel(done) {
      this.fileUpload = null;
      this.cropperOption = {
        outputSize: 1,
        autoCrop: true,
        fixedBox: true,
        autoCropWidth: this.tCropWidth,
        autoCropHeight: this.tCropHeight,
        image: null,
      };
      if (this.cropperOption.image) {
        this.$refs["cropper"].imgs = "";
      }
      this.previews = {};
      done();
    },
			getUniqueID(){//生成11位随机的36进制id+上时间戳的36进制
  return (Math.random().toString(36).substr(2))+Date.now().toString(36)
},
	// 第一个参数文件名,第二个参数是生成唯一ID的函数
filter_fileNameStr(fileName,getUniqueID){// 过滤文件名称的中文及特殊字符 
   console.log("fileName:",fileName)
   var suffix = fileName.substring(fileName.lastIndexOf('.'), fileName.length)//后缀名
   console.log("后缀suffix:",suffix)
   var prefix = fileName.substring(0, fileName.lastIndexOf('.'))//截取后缀名之前的字符,以便于后面做字符处理 前缀名
   console.log("前缀prefix:",prefix)
   var reg = /^[\u4e00-\u9fa5\(\)_a-zA-Z0-9]+$/ //匹配中英文字符用于过滤特殊字符
   var reg_filterSpecial = /[^\u4e00-\u9fa5\(\)_a-zA-Z0-9]+/g //匹配非中英文的特殊字符,过滤特殊字符
   var reg_cn = /[\u4e00-\u9fa5\(\)]+/g //匹配中文字符用于过滤中文字符
   if(!reg.test(prefix)) {//匹配非所有中英文字符,
     console.log("文件含有特殊字符,开始过滤特殊字符,过滤前 前缀名称：",prefix)
     prefix = prefix.replace(reg_filterSpecial, '')//有特殊字符,则替换为空
     console.log("文件含有特殊字符,处理图片名称,过滤后 前缀名称：",prefix)
   }
   if(reg_cn.test(prefix)) {//匹配所有中英文字符,如果图片有中文字符,则替换为空
     console.log("文件含有中文,开始过滤中文,过滤前 前缀名称：",prefix)
     prefix = prefix.replace(reg_cn, '')//有中文字符,则替换为空
     console.log("文件含有中文,处理图片名称,过滤后 前缀名称：",prefix)
   }
   fileName = prefix+'_weteach_'+(getUniqueID().slice(1,10)) + suffix;//前缀加上唯一ID加上后缀名,重新命名完成 weteach是我作品的名称
   return fileName;
},
  },
  watch: {
    isShowCropper(param) {
      //监听isShowCropper状态，以便于实时改变isShowCropper的值
      this.isShowModel = param;
      // console.log("isshow:",this.isShowModel)
    },
  },
	}
</script>

<style lang="less" scoped>
a{
	cursor: pointer;
}
a:hover{
	color: brown;
}
.uploadByCropper {
	position: relative;
  z-index: 2;
  padding-top: 100px;
  position: relative;
  width: 300px;
  margin: 0 auto;
}
.scropper {
  .el-dialog {
    .cropper_content {
      .vue-cropper {
        width: 10rem !important;
        height: 10rem !important;
        .cropper-crop-box {
        width: 8rem !important;
        height: 8rem !important;
        }
        .cropper-modal {
          background: url("data:image/png") !important;
        }
      }
      .el-button {
        margin-top: 10px;
        width: 100%;
        height: 40px;
        font-size: 14px;
        text-align: center;
      }
      .changeImage {
        margin-top: 10px;
        height: 40px;
        line-height: 40px;
        text-align: center;
        font-size: 14px;
        color: #fff;
        background: #67c23a;
        border-color: #67c23a;
        border-radius: 4px;
      }
    }
  }
}
.Cropperupload {
  .btn {
    display: inline-block;
    width: 100%;
    cursor: pointer;
  }
  small {
    zoom: 0.5;
  }
  big {
    zoom: 0.5;
  }
  .imageArea {
    position: relative;
  }
}
.lcropper {
  .el-dialog {
    .cropper_content {
      .vue-cropper {
         width: 15rem !important;
        height: 15rem !important;
        zoom: 0.4;
        .cropper-crop-box {
        width: 15rem !important;
        height: 15rem !important;
        }
        .cropper-modal {
          background: url("data:image/png") !important;
        }
      }
      .el-button {
        margin-top: 10px;
        width: 100%;
        height: 40px;
        font-size: 14px;
        text-align: center;
      }
      .changeImage {
        margin-top: 10px;
        height: 40px;
        line-height: 40px;
        text-align: center;
        font-size: 14px;
        color: #fff;
        background: #67c23a;
        border-color: #67c23a;
        border-radius: 4px;
      }
    }
  }
}
.naocan{
  position: absolute;
  // top: 50%;
  transform: translateY(-50%);
  height: 50%;
  width: 50%;
}
</style>
